package com.cd.university.common.utils;

import com.alibaba.fastjson.JSON;
import com.cd.university.config.security.authentication.jwt.JwtExceptionHandler;
import com.cd.university.config.security.properties.SecurityProperties;
import com.cd.university.service.UserService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Jwt的双令牌机制
 * 一个令牌放在浏览器
 * 另一个缓存在Redis中
 * @author 陈栋
 * @create 2021/9/9 21:27
 */
@Component
public class JwtUtils {

    private ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 用户令牌的算法
     */
    public static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS512);

    /**
     * 刷新令牌的算法
     */
    public static final Key redisKey = Keys.secretKeyFor(SignatureAlgorithm.HS512);

    /**
     * 配置总类
     */
    @Autowired
    private SecurityProperties securityProperties;

    /**
     * 用户的service层
     */
    @Autowired
    private UserService userService;

    public String createAccessToken(UserDetails userDetails) {
        return createJWTToken(userDetails, securityProperties.getJwt().getAccessTokenExpireTime());
    }

    public String createJWTToken(UserDetails userDetails, long timeToExpire) {
        return createJWTToken(userDetails, timeToExpire, key);
    }

    public String createRedisToken(UserDetails userDetails) {
        return createJWTToken(userDetails, securityProperties.getJwt().getRedisTokenExpireTime(), redisKey);
    }

    /**
     * 根据用户信息生成一个 JWT
     * @param user  用户信息
     * @param timeToExpire 毫秒单位的失效时间
     * @param signKey      签名使用的 key
     * @return JWT的token字符串
     */
    public String createJWTToken(UserDetails user, long timeToExpire, Key signKey) {
        // 拿到当前系统时间
        long millis = System.currentTimeMillis();

        // 拿到用户的权限列表
        List<SimpleGrantedAuthority> listAuthorities = (List<SimpleGrantedAuthority>) user
                .getAuthorities()
                .stream()
                .collect(Collectors.toList());

        String [] array = new String[listAuthorities.size()];
        // 把用户权限列表转成字符串数组
        for (int i = 0; i < listAuthorities.size(); i++) {
            array[i] = listAuthorities.get(i).getAuthority();
        }
        // 把字符串数组转成List集合
        List<String> authorities = List.of(array);

        String token = null;
        try {
            token = Jwts.builder()
                    .setId("cd")
                    // 真正放入Jwt中的内容
                    .claim("authorities", authorities)
                    .claim("userDetails", JSON.toJSONString(user))
                    // 设置主题
                    .setSubject(user.getUsername())
                    // 签发时间(生成时间)，一般为当前时间
                    .setIssuedAt(new Date(millis))
                    // 设置过期时间为一天之后
                    .setExpiration(new Date(millis + timeToExpire))
                    // 前面一个参数用于签名，后面一个参数用于规定算法
                    .signWith(signKey, SignatureAlgorithm.HS512)
                    // 压缩一下，之后会返回String
                    .compact();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 使用AES加密之后返回
//        try {
//            token = AesUtil.encrypt(token);
//        } catch (Exception e) {
//            e.printStackTrace();
//            throw new RuntimeException("JWT的Token使用AES加密时失败");
//        }
        return token;
    }

    /**
     * 解析存在token中的权限
     * @param token token
     * @return 返回权限的List<String>集合
     */
    public List<String> getAuthorities(String token){
        Claims body = getClaimsBody(token);
        List<String> authorities = (List<String>) body.get("authorities");
        return authorities;
    }

    /**
     * 解析存在token中的username
     * @param token 令牌
     * @return username字符串
     */
    public UserDetails getUserDetails(String token) {
        // 解密token
//        try {
//            token = AesUtil.decrypt(token);
//        } catch (Exception e) {
//            e.printStackTrace();
//            throw new RuntimeException("JWT的Token解密时失败");
//        }

        Claims body = getClaimsBody(token);
        String userDetailsString = body.get("userDetails",String.class);
        if (userDetailsString==null) {
            throw new JwtExceptionHandler("userDetails解析出错");
        }
        User userDetails = JSON.parseObject(userDetailsString, User.class);
        return userDetails;
    }

    /**
     * 通过用户名查询用户
     * @param username 用户名
     * @return 用户实例
     */
    public com.cd.university.db.pojo.User getUser(String username) {
        return userService.searchUserByUserInfo(username);
    }

    /**
     * 得到jwt的封装信息的body,这里的token已经解密了
     * @param token 令牌
     * @return body
     */
    private Claims getClaimsBody(String token) {
        // 解析
        Jws<Claims> claimsJws = Jwts.parserBuilder()
                .setSigningKey(key) // 指明用什么算法进行解析
                .build()
                .parseClaimsJws(token);
        return claimsJws.getBody();
    }


    /**
     * 用于验证用户的令牌以及刷新令牌（Redis令牌）是否过期
     * @param accessToken 用户令牌
     * @param redisToken redis令牌
     * @return 如果返回null说明两个令牌都已经过期了
     *          如果返回用户令牌那么说明没有过期
     *          如果用户令牌过期了，但是Redis令牌没有过期，那么返回新令牌
     */
    public String  validateToken(String accessToken, String redisToken) {
        // 如果accessToken失效(用户令牌是否失效)
        if (!validateAccessToken(accessToken)) {
            // 判断RedisToken是否也生效(刷新的令牌是否也失效)
            if (!validateRedisToken(redisToken)){
                return  null;
            }else { // refreshToken没有过期
                // 按照refreshKey解析refreshToken里面的信息拿到Claims
                Claims claims = Jwts.parserBuilder().setSigningKey(redisKey).build().parseClaimsJws(redisToken).getBody();
                // 设置新的accessToken
                String NewAccessToekn = Jwts.builder().setClaims(claims).signWith(key, SignatureAlgorithm.HS512)
                        .setIssuedAt(new Date())
                        .setExpiration(new Date(System.currentTimeMillis() + securityProperties.getJwt().getAccessTokenExpireTime()))
                        .compact();
                return NewAccessToekn;
            }
        }else {
            return accessToken;
        }
    }

    /**
     * 用于验证AccessToken(用户token)是否有效
     * @param jwtToken token
     * @return 布尔值，true代表有效，否则无效
     */
    public boolean validateAccessToken(String jwtToken) {
        return validateToken(jwtToken, key);
    }

    /**
     * 用于验证Redis中的Token（刷新令牌）是否有效
     * @param jwtToken token
     * @return 布尔值，true代表有效，否则无效
     */
    public boolean validateRedisToken(String jwtToken) {
        return validateToken(jwtToken, redisKey);
    }

    /**
     * 具体验证
     * @param jwtToken token
     * @param signKey 生成签名时的算法
     * @return 验证是否通过
     */
    public boolean validateToken(String jwtToken, Key signKey) {
//        try {
//            jwtToken = AesUtil.decrypt(jwtToken);
//        } catch (Exception e) {
//            e.printStackTrace();
//            throw new RuntimeException("TOKEN令牌有误，解密出错");
//        }


        try {
            Jwts.parserBuilder().setSigningKey(signKey).build().parseClaimsJws(jwtToken);
            return true;
        } catch (ExpiredJwtException | SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
            return false;
        }
    }
}
